#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <limits.h>

#include "sds.h"

static inline int sdsHdrSize(char type){
	switch(type & SDS_TYPE_MASK){
	case SDS_TYPE_5:
		return sizeof(struct sdshdr5);
	case SDS_TYPE_8:
		return sizeof(struct sdshdr8);
	case SDS_TYPE_16:
		return sizeof(struct sdshdr16);
	}

	return 0;
}

static inline char sdsReqType(size_t string_size){
	if(string_size < 1<< 5){
		return SDS_TYPE_5;
	}else if(string_size < 1<<8){
		return SDS_TYPE_8;
	}else if(string_size < 1<<16){
		return SDS_TYPE_16;
	}
	exit(-1);
}

sds sdsnewlen(const void *init, size_t initlen) {
	void *sh;
	sds s;
	char type = sdsReqType(initlen);
	/* Empty strings are usually created in order to append. Use type 8
	 * since type 5 is not good at this. */
	if(initlen == 0)
		type = SDS_TYPE_8;
	int hdrlen = sdsHdrSize(type);
	unsigned char *fp;       /* flags pointer. */
	sh = malloc(hdrlen + initlen + 1);
	if(!init)
		memset(sh, 0, hdrlen + initlen + 1);
	if(sh == NULL)
		return NULL;
	s = (char*) sh + hdrlen;
	fp = ((unsigned char*)s) - 1;
	switch(type){
	case SDS_TYPE_5:{
		*fp = type | (initlen << SDS_TYPE_BITS);
		break;
	}
	case SDS_TYPE_8:{
		SDS_HDR_VAR(8, s);
		sh->len = initlen;
		sh->alloc = initlen;
		*fp = type;
	}
	case SDS_TYPE_16:{
		SDS_HDR_VAR(16, s);
		sh->len = initlen;
		sh->alloc = initlen;
		*fp = type;
	}
	}
	if(initlen && init){
		memcpy(s, init, initlen);
	}
	s[initlen] = '\0';
	return s;
}


sds sdsnew(const char *init) {
	size_t initlen = (init == NULL) ? 0 : strlen(init);
	return sdsnewlen(init, initlen);
}

/*
 * Create an empty (zero length) sds string. Event in this case the string
 * always has an implicit null termination.
 */
sds sdsempty(){
	return sdsnewlen("", 0);
}

void sdsfree(sds s){
	if(s == NULL)
		return;
	free((char*)s - sdsHdrSize(s[-1]));
}
